/*
Trabalho 2 de Programação Concorrente
Versão sequencial
Grupo 5 - Turma B
*/

#include <iostream>
#include <vector>
#include <fstream>
#include <iostream>
#include <sstream>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <omp.h>
#include <cstring>

using namespace std;

//Estrutura de nó para os vetores
typedef struct
{
    string st;
    int prime;
}node;

//Função que checa se uma string é palíndromo (1 = sim, 0 = não)
//Nota: calcula tanto para o caso de frases
//quanto para o caso de palavras
int check_pal(string st)
{
    //Limite inferior da string
    int i = 0;
    //Limite superior da string
    int j = st.size()-1;

    //Se a palavra é "vazia"
	if(j < 2)
		return 0;
    //Enquanto os limites não se cruzam,
    //realiza a verificação caracter por caracter
    while(i < j)
    {
        //Ignora todo caracter que não se tratar de um dígito ou letra no limite inferior
        while(!isalnum(st[i]) && i < j)
            i++;
        //Ignora todo caracter que não se tratar de um dígito ou letra no limite superior
        while(!isalnum(st[j]) && j > 0)
            j--;
        //Se os caracteres são iguais,
        if(tolower(st.at(i)) == tolower(st.at(j)))
        {
            //avança o inferior e retrocede o superior
            i++;
            j--;
        }
        //Caso eles sejam diferentes,
        else
            //retorna zero
            return 0;
    }
    //Se os limites se cruzaram e não houve divergência, retorna 1
    return 1;
}

//Função para descobrir se um número é primo
//Recebe como parâmetro uma palavra e o vetor de primos
//Retorna o número primo em caso positivo
//ou zero, caso contrário
int get_prime(string st, vector<int> *n)
{
    int val = 0;
    //Soma os valores dos caracteres
    for(int i = 0; i < st.size(); i++)
        val += st.at(i);

    //Realiza uma busca binária no vetor de números primos
    if(binary_search(n->begin(), n->end(), val))
        return val;
    else
        return 0;
}

//Função de processamento de palavras
//Guarda no vetor de palavras os palíndromos e
//os números primos, se for o caso
int process_words(string st, vector<node*> *w, vector<int> *n, int op)
{
    stringstream ss;
    ss << st;
    int nwords = 0;
    //Enquanto há palavras,
    #pragma omp parallel shared(ss, w) reduction(+:nwords)
    {
        while(ss.good())
        {
            string aux;
            #pragma omp critical
            {
            ss >> aux;
            nwords++;
            }
            #pragma omp ordered
            while(aux.size() > 0)
            {
                if(!isalnum(aux.at(0)))
                    aux.erase(0,1);
                else
                    break;
            }
            #pragma omp ordered
            while(aux.size() > 0)
            {
                if(!isalnum(aux.at(aux.size()-1)))
                    aux.erase(aux.size()-1, 1);
                else
                    break;
            }
            //cout << "Palavra: \"" << aux << "\"";
            //verifica se trata-se de um palíndromo
            int r = check_pal(aux);
            //Se sim,
            if(r)
            {
                //Cria um nó
                node *a = new node();
                a->st = aux;
                //Verifica se a soma dos caracteres dá um número primo
                if(op == 2)
                    a->prime = get_prime(aux, n);
                else
                    a->prime = 0;

                //E insere o nó no vetor
                #pragma omp critical
                {
                w->push_back(a);
                }
            }
        }
    }
    nwords--;
    //cout << "palavras lidas nesta frase: " << nwords << endl;
    return nwords;
}

//Função que processa as frases
//Recebe como referência os vetores de frases, palavras e
//números primos obtidos pelo Crivo de Eratóstenes
void process_phrases(vector<node*> *p, vector<node*> *w,
        vector<int> *n, ifstream *is, int op)
{
	int pcnt = 0;
	int wcnt = 0;
    string st;
    //int threshold = 0;
    //Laço principal da função - processa todo o arquivo texto
    #pragma omp parallel shared(is, p) private(st) reduction(+:pcnt)
    {
    while(is->good())
    {
        #pragma omp critical
        {
        //Lê uma linha
        getline(*is, st);
        }
        //pos recebe a posição do primeiro símbolo pontual
        int pos = st.find_first_of(".?!");
        //Laço que adiciona ao vetor uma ou mais frases lidas
        //(frases delimitadas por pontos)
        while(pos != -1)
        {
            string aux = st.substr(0, pos+1);
            while(aux.size() > 0)
            {
                if(!isalnum(aux.at(0)))
                    aux.erase(0, 1);
                else
                    break;
            }
            //Guarda o restante de texto do último bloco lido
            st = st.substr(pos+1);
            while(st.size() > 0)
            {
                if(!isalnum(st.at(0)))
                    st.erase(0, 1);
                else
                    break;
            }
            //Cria um nó
            node *a = new node();
            a->prime = 0;
            a->st = aux;
            #pragma omp critical
            {
            //E o adiciona ao vetor de texto
            p->push_back(a);
            }
            pcnt++;
            //atualiza valor de pos (se há mais  frases)
            pos = st.find_first_of(".?!");
        }
        while(st.size() > 0)
		{
			if(!isalnum(st.at(0)))
				st.erase(0, 1);
			else
				break;
		}
        if(st.size() > 0)
        {
            //Cria um nó
            node *a = new node();
            a->prime = 0;
            a->st = st;
            #pragma omp critical
            {
            //E o adiciona ao vetor de texto (frase sem pontuação)
            p->push_back(a);
            }
            pcnt++;
        }
    }
    }

    int psize = p->size();
    #pragma omp parallel for reduction(+:wcnt)
    for(int i = 0; i < psize; i++)
    {
        //Ponteiro auxiliar com a frase em questão
        node *a = p->at(i);
        //cout << "Frase: \"" << a->st << "\""<< endl;
        //Chamada de função de processamento de palavras
        int cwaux = process_words(a->st, w, n, op);
        wcnt += cwaux;
        //Checa se a frase é palíndroma
        if(op == 1)
        {
            int r = check_pal(a->st);
            //Se não for,
            if(!r)
            {
                //marca como vazia
                p->at(i)->prime = -1;
            }
        }
        else
            p->at(i)->prime = -1;

    }

    //Apaga valores desnecessários
    for(int i = p->size()-1; i >= 0; i--)
    {
        if(p->at(i)->prime == -1)
            p->erase(p->begin()+i);
    }

    //threshold = p->size();

    cout << "Número de frases lidas: " << pcnt << endl;
    cout << "Frases como palindromo: " << p->size() << endl;
    cout << "Palavras lidas no total: " << wcnt << endl;
    cout << "Palíndromos (palavras): " << w->size() << endl;

}

//Crivo de Eratóstenes - Escolhe um primo e elimina seus múltiplos
//Recebe como parâmetro um número que servirá de base para o cálculo
//O resultado será guardado no vetor passado como referência
void eratosthenes_sieve(vector<int> *v, int n)
{
    //Variável de posição do número primo
    int pos = 1;

    v->push_back(2);
    //Preenchimento inicial do vetor - todos os números de 3 à n são carregados
    for(int i = 3; i < n; i+= 2)
        v->push_back(i);

    //Cálculo do critério de parada: ńúmero em questão > raiz do tamanho escolhido
    int sqrt_n = (int)sqrt(n);
    #pragma omp schedule(dynamic)
    {
    do
    {
        //Descobre o primeiro valor a ser eliminado
        int val = v->at(pos);
        //Atribui zero a todos os múltiplos do primo em questão
        #pragma omp parallel for
        for(int i = val+pos; i < n/2; i += val)
        {
            v->at(i) = 0;
        }
        //Força o avanço para cálculo
        pos++;
        //Avança até encontrar o próximo primo a ter seus múltiplos zerados
        while(v->at(pos) == 0)
            pos++;

    } while(v->at(pos) <= sqrt_n);
    }

    int i = 0;
    //Laço que remove todas as ocorrências de zero (Números não-primos)
	while(i < v->size())
	{
        if(v->at(i) == 0)
            v->erase(v->begin()+i);
        else
            i++;
	}
}

int main(int argc, char *argv[])
{
    if(argc < 2)
    {
        cout << "Erro: Número incorreto de parametros. Encerrando o programa." << endl;
        return -1;
    }

    omp_set_num_threads(6);

    int option = atoi(argv[1]);

    //Vetor que conterá os números primos
    vector<int> numbers;
    //Vetor que conterá as frases palíndromas
    vector<node*> phrases;
    //Vetor de palavras que formam palíndromos
    vector<node*> words;

	double start, finish;
  	start = omp_get_wtime();
    //Chamada ao Crivo de Eratóstenes
    eratosthenes_sieve(&numbers, 1000);

    ifstream in_file;
    ofstream wfile, pfile;
    char fn[12];
    //Abre o arquivo a ser analisado
    if(option == 1)
    {
        in_file.open("shakespe.txt");
        strcpy(fn, "shakespe_");
    }
    else if(option == 2)
    {
        in_file.open("wikipedia.txt");
        strcpy(fn, "wikipedia_");
    }
    else
    {
        cout << "Erro: opcao invalida de arquivo! Encerrando o programa." << endl;
        return -1;
    }
    //Chamada ao processo de texto
    process_phrases(&phrases, &words, &numbers, &in_file, option);
	finish = omp_get_wtime();

    //cout << "Tempo de execucao: " << (double((finish-start)/CLOCKS_PER_SEC)) << "s" << endl;
    cout << "Tempo de execucao: " << (double(finish-start)) << "s" << endl;

    char fname[32];
    strcpy(fname, fn);
    strcat(fname, "words.txt");
    wfile.open(fname);
    if(option == 2)
    {
        for(int i = 0; i < words.size(); i++)
            wfile << words.at(i)->st << ", " << words.at(i)->prime << endl;
    }
    else
    {
        for(int i = 0; i < words.size(); i++)
            wfile << words.at(i)->st << endl;
    }
    wfile.close();

    if(option == 1)
    {
        strcpy(fname, fn);
        strcat(fname, "phrases.txt");
        pfile.open(fname);
        for(int i = 0; i < phrases.size(); i++)
            pfile << phrases.at(i)->st << endl;
        pfile.close();
    }

    return 0;
}
